{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "_DDaAex5Q7u-"
      },
      "source": [
        "##### Copyright 2019 The TensorFlow Authors."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "W1dWWdNHQ9L0"
      },
      "outputs": [],
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "6Y8E0lw5eYWm"
      },
      "source": [
        "# 訓練後の整数量子化"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "CIGrZZPTZVeO"
      },
      "source": [
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://www.tensorflow.org/lite/performance/post_training_integer_quant\"><img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\" />View on TensorFlow.org</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs-l10n/blob/master/site/ja/lite/performance/post_training_integer_quant.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\" />Run in Google Colab</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://github.com/tensorflow/docs-l10n/blob/master/site/ja/lite/performance/post_training_integer_quant.ipynb\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\" />View source on GitHub</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a href=\"https://storage.googleapis.com/tensorflow_docs/docs-l10n/site/ja/lite/performance/post_training_integer_quant.ipynb\"><img src=\"https://www.tensorflow.org/images/download_logo_32px.png\" />Download notebook</a>\n",
        "  </td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "BTC1rDAuei_1"
      },
      "source": [
        "## 概要\n",
        "\n",
        "現在、[TensorFlow Lite](https://www.tensorflow.org/lite/) は TensorFlow から TensorFlow Lite のフラットバッファー形式へ変換する際に、\n",
        "モデルのすべての値(重みと活性化関数)を8ビット整数へ変換することをサポートしています。\n",
        "これによりモデルの大きさが4倍小さくなり、CPU上での性能が3~4倍改善します。\n",
        "さらに、この完全に量子化されたモデルは整数演算のみに対応したハードウェアアクセラレータで使用できます。\n",
        "\n",
        "重みのみを8bitで保持する [訓練後の \"オン・ザ・フライ\" 量子化](https://colab.sandbox.google.com/github/tensorflow/tensorflow/blob/master/tensorflow/lite/tutorials/post_training_quant.ipynb) とは対照的に、\n",
        "この手法はモデル変換中に静的にすべての重み `と` 活性化関数を量子化します。\n",
        "\n",
        "このチュートリアルでは、MNIST モデルをスクラッチから訓練し、その精度を TensorFlow で検査しそれから、モデルを完全に量子化された TensorFlow Lite のフラットバッファーに変換します。\n",
        "最後に変換されたモデルの精度を検査し、それを元の浮動小数点数モデルと比較します。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "2XsEP17Zelz9"
      },
      "source": [
        "## MNIST モデルをビルドする"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "dDqqUIZjZjac"
      },
      "source": [
        "### セットアップ"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "WsN6s5L1ieNl"
      },
      "outputs": [],
      "source": [
        "import logging\n",
        "logging.getLogger(\"tensorflow\").setLevel(logging.DEBUG)\n",
        "\n",
        "import tensorflow as tf\n",
        "from tensorflow import keras\n",
        "import numpy as np\n",
        "import pathlib"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "eQ6Q0qqKZogR"
      },
      "source": [
        "### モデルを訓練してエクスポートする"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "eMsw_6HujaqM"
      },
      "outputs": [],
      "source": [
        "# MNIST データセットを読み込みます\n",
        "mnist = keras.datasets.mnist\n",
        "(train_images, train_labels), (test_images, test_labels) = mnist.load_data()\n",
        "\n",
        "# 入力画像を正規化することにより、各画素値は0から1の間の値になります\n",
        "train_images = train_images / 255.0\n",
        "test_images = test_images / 255.0\n",
        "\n",
        "# モデル構造を定義します\n",
        "model = keras.Sequential([\n",
        "  keras.layers.InputLayer(input_shape=(28, 28)),\n",
        "  keras.layers.Reshape(target_shape=(28, 28, 1)),\n",
        "  keras.layers.Conv2D(filters=12, kernel_size=(3, 3), activation='relu'),\n",
        "  keras.layers.MaxPooling2D(pool_size=(2, 2)),\n",
        "  keras.layers.Flatten(),\n",
        "  keras.layers.Dense(10)\n",
        "])\n",
        "\n",
        "# 数値分類モデルを訓練します\n",
        "model.compile(optimizer='adam',\n",
        "              loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),\n",
        "              metrics=['accuracy'])\n",
        "model.fit(\n",
        "  train_images,\n",
        "  train_labels,\n",
        "  epochs=1,\n",
        "  validation_data=(test_images, test_labels)\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "5NMaNZQCkW9X"
      },
      "source": [
        "モデルを1エポックだけ訓練するので、この訓練は時間がかかりませんし、およそ96%の精度に達します。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "xl8_fzVAZwOh"
      },
      "source": [
        "### TensorFlow Lite モデルに変換する\n",
        "\n",
        "Python の [TFLiteConverter](https://www.tensorflow.org/lite/convert/python_api) を使って、訓練されたモデルを TensorFlow Lite のモデルに変換することができます。\n",
        "\n",
        "`TFLiteConverter` を使ってモデルを読み込みましょう:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "_i8B2nDZmAgQ"
      },
      "outputs": [],
      "source": [
        "converter = tf.lite.TFLiteConverter.from_keras_model(model)\n",
        "tflite_model = converter.convert()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "F2o2ZfF0aiCx"
      },
      "source": [
        "`.tflite` file に書き出します:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "vptWZq2xnclo"
      },
      "outputs": [],
      "source": [
        "tflite_models_dir = pathlib.Path(\"/tmp/mnist_tflite_models/\")\n",
        "tflite_models_dir.mkdir(exist_ok=True, parents=True)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Ie9pQaQrn5ue"
      },
      "outputs": [],
      "source": [
        "tflite_model_file = tflite_models_dir/\"mnist_model.tflite\"\n",
        "tflite_model_file.write_bytes(tflite_model)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "PCnZWNKkkRYG"
      },
      "source": [
        "さて、`.tfile` に変換された訓練された MNIST モデルを取得しましたが、それはまだすべてのパラメータに対して単精度浮動小数点数を使っています。\n",
        "\n",
        "今度は量子化を使って、モデルを再度変換しましょう\n",
        "\n",
        "#### 量子化を使って変換する\n",
        "\n",
        "まず、最初に大きさを最適化するために `optimizations` フラグをセットします。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "HEZ6ET1AHAS3"
      },
      "outputs": [],
      "source": [
        "converter.optimizations = [tf.lite.Optimize.DEFAULT]"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "rTe8avZJHMDO"
      },
      "source": [
        "さて、活性化関数の正確なダイナミックレンジをもつ量子化値を作るために、\n",
        "代表的なデータセットを提供する必要があります。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "FiwiWU3gHdkW"
      },
      "outputs": [],
      "source": [
        "mnist_train, _ = tf.keras.datasets.mnist.load_data()\n",
        "images = tf.cast(mnist_train[0], tf.float32) / 255.0\n",
        "mnist_ds = tf.data.Dataset.from_tensor_slices((images)).batch(1)\n",
        "def representative_data_gen():\n",
        "  for input_value in mnist_ds.take(100):\n",
        "    yield [input_value]\n",
        "\n",
        "converter.representative_dataset = representative_data_gen"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "xW84iMYjHd9t"
      },
      "source": [
        "最後に、モデルを TensorFlow Lite 形式に変換します:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "yuNfl3CoHNK3"
      },
      "outputs": [],
      "source": [
        "tflite_model_quant = converter.convert()\n",
        "tflite_model_quant_file = tflite_models_dir/\"mnist_model_quant.tflite\"\n",
        "tflite_model_quant_file.write_bytes(tflite_model_quant)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "PhMmUTl4sbkz"
      },
      "source": [
        "結果として得られるファイルは、およそ `1/4' のサイズになっていることに注意してください。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "JExfcfLDscu4"
      },
      "outputs": [],
      "source": [
        "!ls -lh {tflite_models_dir}"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "RACBJuj2XO8x"
      },
      "source": [
        "モデルは完全に量子化されているでしょう。\n",
        "しかし、もしモデルが TensorFlow Lite が量子化できない演算を含む場合には、\n",
        "それらの演算は浮動小数点数のまま残されています。\n",
        "これにより小さく効率的なモデルを作り上げることができますが、\n",
        "そのモデルは完全な整数量子化を必要とするいくつかの機械学習アクセラレータとは互換性がないでしょう。\n",
        "また、デフォルトでは、変換されたモデルはまだ浮動小数点数の入力と出力を使用しており、\n",
        "いくつかのアクセラレータとは互換性がありません。\n",
        "\n",
        "変換されたモデルが完全に量子化されているか確認するため(量子化されていない演算に遭遇したら変換器にエラーを投げさせる)、\n",
        "整数をモデルの入力と出力に使用して、モデルに追加設定を行ってからもう一度変換するする必要があります:\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "kzjEjcDs3BHa"
      },
      "outputs": [],
      "source": [
        "converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]\n",
        "converter.inference_input_type = tf.uint8\n",
        "converter.inference_output_type = tf.uint8\n",
        "\n",
        "tflite_model_quant = converter.convert()\n",
        "tflite_model_quant_file = tflite_models_dir/\"mnist_model_quant_io.tflite\"\n",
        "tflite_model_quant_file.write_bytes(tflite_model_quant)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "wYd6NxD03yjB"
      },
      "source": [
        "このサンプルでは、結果として得られるモデルの大きさはおなじままです。\n",
        "すべての演算が量子化されるからです。\n",
        "しかし、この新しいモデルは量子化された入力と出力を使用し、Coral Edge TPU のような多くのアクセラレータと互換があります。\n",
        "\n",
        "以降の節では、2つのTensorFlow Liteモデルを扱うことに注意してください:\n",
        "`tflite_model_file` は、浮動小数点数パラメータをまだ使用している変換モデルで、\n",
        "`tflite_model_quant_file` は、uint8の入力と出力を含む完全に整数量子化されて変換されたおなじモデルです。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "L8lQHMp_asCq"
      },
      "source": [
        "## TFLite モデルを実行する\n",
        "\n",
        "TensorFlow Lite モデルを Python の TensorFlow Lite\n",
        "インタープリタを使って実行します。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Ap_jE7QRvhPf"
      },
      "source": [
        "### モデルをインタープリタに読み込む"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Jn16Rc23zTss"
      },
      "outputs": [],
      "source": [
        "interpreter = tf.lite.Interpreter(model_path=str(tflite_model_file))\n",
        "interpreter.allocate_tensors()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "J8Pztk1mvNVL"
      },
      "outputs": [],
      "source": [
        "interpreter_quant = tf.lite.Interpreter(model_path=str(tflite_model_quant_file))\n",
        "interpreter_quant.allocate_tensors()\n",
        "input_index_quant = interpreter_quant.get_input_details()[0][\"index\"]\n",
        "output_index_quant = interpreter_quant.get_output_details()[0][\"index\"]"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "2opUt_JTdyEu"
      },
      "source": [
        "### 1つの画像に対してモデルを検証する\n",
        "\n",
        "まず浮動小数点数モデルで検証します:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "AKslvo2kwWac"
      },
      "outputs": [],
      "source": [
        "test_image = np.expand_dims(test_images[0], axis=0).astype(np.float32)\n",
        "\n",
        "input_index = interpreter.get_input_details()[0][\"index\"]\n",
        "output_index = interpreter.get_output_details()[0][\"index\"]\n",
        "interpreter.set_tensor(input_index, test_image)\n",
        "interpreter.invoke()\n",
        "predictions = interpreter.get_tensor(output_index)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "XZClM2vo3_bm"
      },
      "outputs": [],
      "source": [
        "import matplotlib.pylab as plt\n",
        "\n",
        "plt.imshow(test_images[0])\n",
        "template = \"True:{true}, predicted:{predict}\"\n",
        "_ = plt.title(template.format(true= str(test_labels[0]),\n",
        "                              predict=str(np.argmax(predictions[0]))))\n",
        "plt.grid(False)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "o3N6-UGl1dfE"
      },
      "source": [
        "今度は量子化されたモデル(uint8データを使用する)を検証します:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "3gwhv4lKbYZ4"
      },
      "outputs": [],
      "source": [
        "input_index = interpreter_quant.get_input_details()[0][\"index\"]\n",
        "output_index = interpreter_quant.get_output_details()[0][\"index\"]\n",
        "interpreter_quant.set_tensor(input_index, test_image)\n",
        "interpreter_quant.invoke()\n",
        "predictions = interpreter_quant.get_tensor(output_index)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "CIH7G_MwbY2x"
      },
      "outputs": [],
      "source": [
        "plt.imshow(test_images[0])\n",
        "template = \"True:{true}, predicted:{predict}\"\n",
        "_ = plt.title(template.format(true= str(test_labels[0]),\n",
        "                              predict=str(np.argmax(predictions[0]))))\n",
        "plt.grid(False)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "LwN7uIdCd8Gw"
      },
      "source": [
        "### モデルを評価する"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "05aeAuWjvjPx"
      },
      "outputs": [],
      "source": [
        "# \"test\" データセットを使って TF Lite モデルを評価するヘルパー関数\n",
        "def evaluate_model(interpreter):\n",
        "  input_index = interpreter.get_input_details()[0][\"index\"]\n",
        "  output_index = interpreter.get_output_details()[0][\"index\"]\n",
        "\n",
        "  # \"test\" データセットの画像ごとに予測を実行する\n",
        "  prediction_digits = []\n",
        "  for test_image in test_images:\n",
        "    # 前処理: バッチの次元を追加し、単精度浮動小数点数に変換し、\n",
        "    # モデルの入力データフォーマットに合わせます\n",
        "    test_image = np.expand_dims(test_image, axis=0).astype(np.float32)\n",
        "    interpreter.set_tensor(input_index, test_image)\n",
        "\n",
        "    # 推論を実行します。\n",
        "    interpreter.invoke()\n",
        "\n",
        "    # 後処理: バッチの次元を取り除き、最も出力の高い数字を見つける\n",
        "    # probability.\n",
        "    output = interpreter.tensor(output_index)\n",
        "    digit = np.argmax(output()[0])\n",
        "    prediction_digits.append(digit)\n",
        "\n",
        "  # 予測結果を正解ラベルと比較し、精度を計算します。\n",
        "  accurate_count = 0\n",
        "  for index in range(len(prediction_digits)):\n",
        "    if prediction_digits[index] == test_labels[index]:\n",
        "      accurate_count += 1\n",
        "  accuracy = accurate_count * 1.0 / len(prediction_digits)\n",
        "\n",
        "  return accuracy"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "T5mWkSbMcU5z"
      },
      "outputs": [],
      "source": [
        "print(evaluate_model(interpreter))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Km3cY9ry8ZlG"
      },
      "source": [
        "uint8データを使用した完全に量子化されたモデルで評価を繰り返します:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "-9cnwiPp6EGm"
      },
      "outputs": [],
      "source": [
        "# NOTE: Colab はサーバーのCPUで実行します。そして、TensorFlow Lite は現在、\n",
        "# 非常に最適化されたサーバーCPUカーネルはありません。そのためこの箇所は\n",
        "# 上記の浮動小数点数インタープリタよりも遅いかもしれません。しかし、モバイルCPUでは、\n",
        "# かなりの高速化を観測できるでしょう。\n",
        "\n",
        "print(evaluate_model(interpreter_quant))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "L7lfxkor8pgv"
      },
      "source": [
        "このサンプルでは、上記の浮動小数点数モデルと比較して、精度にほとんど差がないモデルを完全に量子化できました。"
      ]
    }
  ],
  "metadata": {
    "colab": {
      "collapsed_sections": [],
      "name": "post_training_integer_quant.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
